package al.work.httplisten;

import al.work.common.define.Pair;
import al.work.common.task.TaskThread;
import al.work.common.util.GsonUtils;
import al.work.common.util.UrlParamMapUtils;
import al.work.httplisten.annotation.HttpMethod;
import al.work.httplisten.annotation.RequestParam;
import al.work.httplisten.aop.HttpAspectHandler;
import al.work.httplisten.holder.RequestContextHolder;
import com.google.common.base.Charsets;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class HttpJsonHandler implements HttpHandler {

    /**
     * 任务处理器
     */
    private TaskThread task;
    /**
     * 全局异常处理
     */
    private HttpExceptionHandler exceptionHandler;
    /**
     * aop处理
     */
    private HttpAspectHandler aspectHandler;
    /**
     * 处理控制器对象
     */
    private Object controller;
    /**
     * 处理方法
     */
    private Method method;
    /**
     * 允许的http请求类型
     */
    private HttpMethod hm;
    /**
     * 请求参数类型和位置
     */
    private Pair<Class<?>, Integer> paramPair;
    /**
     * exchange 回调的参数位置
     */
    private Integer exchangeIndex;

    public HttpJsonHandler(TaskThread task, HttpExceptionHandler exceptionHandler, HttpAspectHandler aspectHandler, Object controller, Method method) {
        this.task = task;
        this.exceptionHandler = exceptionHandler;
        this.aspectHandler = aspectHandler;
        this.controller = controller;
        this.method = method;

        this.hm = getHttpMethod();
        this.paramPair = getParamType();
    }

    @Override
    public void handle(HttpExchange exchange) {
        boolean result = task.addTask(() -> {
            RequestContextHolder.setHttpExchange(exchange);
            this.handlerInl(exchange);
        });
        if (!result) {
            exchange.close();
        }
    }

    private void handlerInl(HttpExchange exchange) throws IOException {

        if (exchange.getRequestMethod().equalsIgnoreCase("GET")) {
            if (null != hm && hm.type() != HttpMethod.TypeDefine.ALL && hm.type() != HttpMethod.TypeDefine.GET) {
                errorHandler(exchange, 404);
                return;
            }
        } else if(exchange.getRequestMethod().equalsIgnoreCase("POST")) {
            if (null != hm && hm.type() != HttpMethod.TypeDefine.ALL && hm.type() != HttpMethod.TypeDefine.POST) {
                errorHandler(exchange, 404);
                return;
            }
        }

        Headers headers = exchange.getResponseHeaders();
        headers.set("Content-Type", "application/json; charset=utf-8");
        headers.set("Access-Control-Allow-Origin", "*");
        headers.set("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS");
        headers.set("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept");

        Object args[] = new Object[method.getParameterTypes().length];

        Object request = null;
        if (null != this.paramPair) {
            String requestBody = new String(readStream(exchange.getRequestBody()));
            Map<String, Object> query = UrlParamMapUtils.transforToMap(exchange.getRequestURI().getQuery());
            Map body = GsonUtils.fromJson(requestBody, Map.class);
            if (null !=body) {
                query.putAll(body);
            }
            request = GsonUtils.fromJson(GsonUtils.toJson(query), this.paramPair.left);

            args[this.paramPair.right] = request;
        }

        Object result = execMethod(method, args);
        byte[] send = GsonUtils.toJson(result).getBytes(Charsets.UTF_8);

        OutputStream out = null;
        try {
            out = exchange.getResponseBody();
            if (null != out) {
                exchange.sendResponseHeaders(200, send.length);
                out.write(send);
            }
        } finally {
            if (null != out) {
                out.close();
            }
        }
    }

    private Object execMethod(Method method, Object[] args) {
        Object result;
        try {
            //@Before
            aspectHandler.handle(HttpAspectHandler.AspectState.ASPECT_STATE_BEFORE, controller, method, args, null);

            result = method.invoke(controller, args);

            //@AfterReturning
            aspectHandler.handle(HttpAspectHandler.AspectState.ASPECT_STATE_AFTER_RETURING, controller, method, args, result);
            return result;
        } catch (InvocationTargetException | IllegalAccessException e) {
            //@AfterThrowing
            Throwable throwable = e.getCause();
            aspectHandler.handle(HttpAspectHandler.AspectState.ASPECT_STATE_AFTER_THROWING, controller, method, args, throwable);

            result = exceptionHandler.handler(throwable);
        } finally {
            //@After
            aspectHandler.handle(HttpAspectHandler.AspectState.ASPECT_STATE_AFTER, controller, method, args, null);
        }
        return result;
    }

    /**
     * 错误处理
     * @param exchange
     * @param code
     */
    protected void errorHandler(HttpExchange exchange, Integer code) {
        OutputStream out = exchange.getResponseBody();
        try {
            exchange.sendResponseHeaders(code, 0);
            out.close();
        } catch (Exception ex) { }
    }

    public HttpMethod getHttpMethod() {
        return method.getAnnotation(HttpMethod.class);
    }

    public Pair<Class<?>, Integer> getParamType() {
        Annotation[][] annotations = method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; ++i) {
            for (Annotation annotation : annotations[i]) {
                if (annotation.annotationType().equals(RequestParam.class)) {
                    return new Pair<>(method.getParameterTypes()[i], i);
                }
            }
        }
        return null;
    }

    public Integer getExchangeIndex() {
        Class<?>[] classiss = method.getParameterTypes();
        for (Integer i = 0; i < classiss.length; ++i) {
            if (classiss[i].equals(HttpExchange.class)) {
                return i;
            }
        }
        return null;
    }

    /**
     * 读取完整的流数据
     * @param stream
     * @return
     * @throws IOException
     */
    public static byte[] readStream(InputStream stream) throws IOException {
        byte[] buffer = new byte[1024];
        int len = 0;
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        while((len = stream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        outStream.close();
        return outStream.toByteArray();
    }
}
